﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace Calais.RDFSProcess
{
    /// <summary>
    /// Does processing of RDFS file and creates collection of CalaisInterface items.
    /// </summary>
    public class CalaisInterfaceBuilder
    {
        private readonly XNamespace rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
        private readonly XNamespace rdfs = "http://www.w3.org/2000/01/rdf-schema#";
        private const string rdfSchema = "http://www.w3.org/2000/01/rdf-schema#";
        private const string rdfLiteral = "http://www.w3.org/2000/01/rdf-schema#Literal";
        private const string rdfResource = "http://www.w3.org/2000/01/rdf-schema#Resource";
        private const string calaisEntity = "http://s.opencalais.com/1/type/em/e/";
        private const string calaisRelationship = "http://s.opencalais.com/1/type/em/r/";
        private const string calaisSystemEntity = "http://s.opencalais.com/1/type/sys/";
        private const string customTypeBaseName = "CalaisCustomTypeBase";

        private readonly string _prefix;
        private Dictionary<string, CalaisInterface> _interfaceCollection;

        #region Constructors

        public CalaisInterfaceBuilder()
        {
            _prefix = "ICalais";
            _interfaceCollection = new Dictionary<string, CalaisInterface>();
        }

        public CalaisInterfaceBuilder(string prefix)
        {
            _prefix = prefix;
            _interfaceCollection = new Dictionary<string, CalaisInterface>();
        }

        #endregion

        public void Process()
        {
            Process(string.Empty);
        }

        public void Process(string name)
        {
            XNamespace calais;

            var doc = XDocument.Load("RDFS.xml");
            this.Ensure(i => doc != null, new Exception("Unable to process response!"));

            calais = doc.Root.Descendants(rdf + "Description").First().Attribute(rdf + "about").Value;

            IEnumerable<XElement> classes;

            if (string.IsNullOrEmpty(name))
            {
                classes = from item in doc.Root.Descendants(rdfs + "Class")
                          select item;
            }
            else
            {
                classes = from item in doc.Root.Descendants(rdfs + "Class")
                          where item.Attribute(rdf + "about").Value.EndsWith(name)
                          select item;
            }

            if (classes.Count() == 0)
            {
                return;
            }

            foreach (var item in classes)
            {
                string about = item.Attribute(rdf + "about").Value;
                string label = item.Element(rdfs + "label").Value;
                string comment = item.Element(rdfs + "comment").Value;

                if (!IsInterfaceRegistered(label))
                {
                    RegisterInterface(label);
                    AddInterfaceType(label, about);
                    AddInterfaceDescription(label, comment);
                }

                var properties = from i in doc.Root.Descendants(rdf + "Property")
                                 select i;

                foreach (var property in properties)
                {
                    foreach (var domain in property.Elements(rdfs + "domain"))
                    {
                        if (domain.Attribute(rdf + "resource").Value == about)
                        {
                            AddProperty(label, property);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Retreives all CalaisInterface objects from the collection that are marked as being custom (ie non-Calais supported).
        /// </summary>
        /// <returns></returns>
        public IEnumerable<CalaisInterface> GetCustomInterfaces()
        {
            foreach (var @interface in _interfaceCollection)
            {
                if(@interface.Value.IsCustomType)
                {
                    yield return @interface.Value;
                }
            }
        }

        public bool IsInterfaceRegistered(string interfaceName)
        {
            interfaceName = interfaceName.Replace("-dis", "Dis");
            interfaceName = FixName(interfaceName);

            if (interfaceName.StartsWith(_prefix))
            {
                return _interfaceCollection.ContainsKey(interfaceName);
            }

            return _interfaceCollection.ContainsKey(_prefix + interfaceName);
        }

        public CalaisInterface GetInterface(string interfaceName)
        {
            interfaceName = interfaceName.Replace("-dis", "Dis");
            interfaceName = FixName(interfaceName);

            if (interfaceName.StartsWith(_prefix))
            {
                if (IsInterfaceRegistered(interfaceName))
                {
                    return _interfaceCollection[interfaceName];
                }
            }
            else
            {
                if (IsInterfaceRegistered(interfaceName))
                {
                    return _interfaceCollection[_prefix + interfaceName];
                }
            }

            return null;
        }

        public CalaisInterface RegisterInterface(string interfaceName)
        {
            return RegisterInterface(interfaceName, false);
        }

        public CalaisInterface RegisterInterface(string interfaceName, bool isCustomType)
        {
            interfaceName = interfaceName.Replace("-dis", "Dis");
            interfaceName = FixName(interfaceName);

            var newInterface = new CalaisInterface(_prefix + interfaceName);
            newInterface.IsCustomType = isCustomType;

            if (!IsInterfaceRegistered(interfaceName))
            {
                _interfaceCollection.Add(_prefix + interfaceName, newInterface);
                return newInterface;
            }

            return GetInterface(interfaceName);
        }

        public void AddInterfaceType(string interfaceName, string type)
        {
            interfaceName = interfaceName.Replace("-dis", "Dis");
            interfaceName = FixName(interfaceName);

            if (IsInterfaceRegistered(interfaceName))
            {
                _interfaceCollection[_prefix + interfaceName].CalaisType = type;
            }
        }

        public void AddInterfaceDescription(string interfaceName, string description)
        {
            interfaceName = interfaceName.Replace("-dis", "Dis");
            interfaceName = FixName(interfaceName);

            if (IsInterfaceRegistered(interfaceName))
            {
                _interfaceCollection[_prefix + interfaceName].CalaisDescription = description;
            }
        }

        internal void AddProperty(string interfaceName, XElement propertyContainer)
        {
            interfaceName = interfaceName.Replace("-dis", "Dis");
            interfaceName = FixName(interfaceName);

            if (IsInterfaceRegistered(interfaceName))
            {
                var iface = GetInterface(interfaceName);

                var propNameElement = propertyContainer.Element(rdfs + "label");
                var propCommentElement = propertyContainer.Element(rdfs + "comment");
                var propTypes = propertyContainer.Elements(rdfs + "range");

                string propertyName;
                string propertyDescription;

                if ((propNameElement == null) || (propCommentElement == null) || (propTypes.Count() <= 0))
                {
                    return;
                }

                propertyName = FixName(CultureInfo.CurrentCulture.TextInfo.ToTitleCase(propNameElement.Value));
                propertyDescription = propCommentElement.Value;

                //Some properties can have multiple possible types
                if (propTypes.Count() > 1)
                {
                    //Create custom type
                    CalaisInterface newCustomInterface;

                    if (!IsInterfaceRegistered("Custom" + propertyName))
                    {
                        newCustomInterface = RegisterInterface("Custom" + propertyName, true);
                        AddInterfaceDescription("Custom" + propertyName, "Custom composite type for [" + propertyName + "]");
                    }
                    else
                    {
                        newCustomInterface = GetInterface("Custom" + propertyName);
                    }

                    foreach (var propType in propTypes)
                    {
                        var propertyType = propType.Attribute(rdf + "resource").Value;

                        if (propertyType == rdfLiteral)
                        {
                            //if (!IsInterfaceRegistered("CustomLiteralString"))
                            //{
                            //    newCustomInterface = RegisterInterface("CustomLiteralString");
                            //    AddInterfaceDescription("CustomLiteralString", "Custom type for a literal string");
                            //}

                            //Console.WriteLine(interfaceName);
                            //Console.WriteLine(propertyName + " : " + propertyType);

                            //newCustomInterface.AddProperty(interfaceName+"Literal", "string");
                            

                            //TODO: DO SOMETHING WITH THIS ? LITERAL TYPE
                            continue;
                        }

                        var propertyTypeName = propertyType.Replace(calaisEntity, string.Empty).Replace(calaisSystemEntity, string.Empty).Replace(calaisRelationship, string.Empty);

                        if (propertyType.StartsWith(rdfSchema))
                        {
                            propertyType = propertyType.Replace(rdfSchema, string.Empty);
                        }

                        if (!IsInterfaceRegistered(propertyTypeName))
                        {
                            Process(propertyType);
                        }

                        var currentIterface = GetInterface(propertyTypeName);
                        currentIterface.AddAdditionalInterface(_prefix + "Custom" + propertyName);

                        //Grab satelitte interface properties and add to custom one.
                        foreach (var property in currentIterface.Properties)
                        {
                            newCustomInterface.AddProperty(property.Key, property.Value);
                        }

                    }

                    iface.AddProperty(FixName(propertyName), _prefix + "Custom" + propertyName);

                }
                else
                {
                    string propertyType = propTypes.First().Attribute(rdf + "resource").Value;

                    //Check if literal value
                    if (propertyType == rdfLiteral)
                    {
                        iface.AddProperty(FixName(propertyName), "string");
                        return;
                    }

                    var propertyTypeName = FixName(propertyType.Replace(calaisEntity, string.Empty).Replace(calaisSystemEntity, string.Empty).Replace(calaisRelationship, string.Empty));

                    if (propertyType.StartsWith(rdfSchema))
                    {
                        iface.AddProperty(FixName(propertyName), "string");
                        return;
                    }

                    if (!IsInterfaceRegistered(propertyTypeName))
                    {
                        Process(propertyType);
                    }

                    iface.AddProperty(FixName(propertyName), _prefix + propertyTypeName);
                }
            }
        }

        internal void AddAdditionalInterface(string interfaceName, string additionalInterfaceName)
        {
            interfaceName = interfaceName.Replace("-dis", "Dis");
            interfaceName = FixName(interfaceName);

            additionalInterfaceName = additionalInterfaceName.Replace("-dis", "Dis");
            additionalInterfaceName = FixName(additionalInterfaceName);

            if (IsInterfaceRegistered(interfaceName))
            {
                _interfaceCollection[_prefix + interfaceName].AddAdditionalInterface(_prefix + additionalInterfaceName);
            }
        }

        /// <summary>
        /// Does some cosmetic tidying of property names.
        /// </summary>
        /// <param name="propertyName">Property name.</param>
        /// <returns></returns>
        public string FixName(string propertyName)
        {
            var NameVersions = new Dictionary<string, string>();
            NameVersions.Add("name", "Name");
            NameVersions.Add("distribution", "Distribution");
            NameVersions.Add("search", "Search");
            NameVersions.Add("request", "Request");
            NameVersions.Add("id", "Id");
            NameVersions.Add("score", "Score");
            NameVersions.Add("relevance", "Relevance");
            NameVersions.Add("metadata", "Metadata");
            NameVersions.Add("date", "Date");
            NameVersions.Add("title", "Title");
            NameVersions.Add("format", "Format");
            NameVersions.Add("baseurl", "BaseUrl");
            NameVersions.Add("type", "Type");
            NameVersions.Add("string", "String");
            NameVersions.Add("metric", "Metric");
            NameVersions.Add("trend", "Trend");
            NameVersions.Add("charges", "Charges");
            NameVersions.Add("share", "Share");
            NameVersions.Add("ratio", "Ratio");
            NameVersions.Add("relation", "Relation");
            NameVersions.Add("description", "Description");
            NameVersions.Add("class", "Class");
            NameVersions.Add("location", "Location");
            NameVersions.Add("meeting", "Meeting");
            NameVersions.Add("orstate", "OrState");
            NameVersions.Add("exchange", "Exchange");
            NameVersions.Add("object", "Object");
            NameVersions.Add("subject", "Subject");
            NameVersions.Add("disaster", "Disaster");
            NameVersions.Add("album", "Album");
            NameVersions.Add("group", "Group");
            NameVersions.Add("company", "Company");
            NameVersions.Add("organization", "Organization");
            NameVersions.Add("split", "Split");
            NameVersions.Add("orcompany", "OrCompany");
            NameVersions.Add("bycountry", "ByCountry");
            NameVersions.Add("bystate", "ByState");

            string newName = propertyName;

            foreach (var version in NameVersions)
            {
                if (propertyName.Contains(version.Key))
                {
                    newName = newName.Replace(version.Key, version.Value);
                }
            }

            return newName;
        }

        public string GenerateAllClasses()
        {
            var sb = new StringBuilder();

            foreach (var collection in _interfaceCollection)
            {
                sb.Append(GenerateClass(collection.Key));
                sb.AppendLine();
            }

            sb.Append(GenerateBaseClass());

            return sb.ToString();
        }

        public string GenerateAllInterfaces()
        {
            var sb = new StringBuilder();

            foreach (var collection in _interfaceCollection)
            {
                sb.Append(GenerateInterface(collection.Key));
                sb.AppendLine();
            }

            return sb.ToString();
        }

        public string GenerateBaseClass()
        {
            var sb = new StringBuilder();
            var allProperties = new Dictionary<string, string>();
            var customInterfaces = GetCustomInterfaces().ToList();

            sb.AppendFormat("  public class {0}", customTypeBaseName);

            sb.Append(" : ");
            for (int i = 0; i < customInterfaces.Count(); i++)
            {
                sb.Append(customInterfaces[i].Name);
                if (i != (customInterfaces.Count - 1))
                {
                    sb.Append(", ");
                }
            }

            sb.AppendLine();
            sb.AppendLine("  {");

            foreach (var additionalInterface in customInterfaces)
            {
                foreach (var property in additionalInterface.Properties)
                {
                    if (!allProperties.ContainsKey(property.Key))
                    {
                        allProperties.Add(property.Key, property.Value);
                    }
                }
            }

            foreach (var property in allProperties)
            {
                sb.AppendFormat("\tpublic {0} {1}", property.Value, property.Key);
                sb.AppendLine(" { get; set; }");
            }

            sb.AppendLine("  }");

            return sb.ToString();

        }

        public string GenerateClass(string interfaceName)
        {
            interfaceName = interfaceName.Replace("-dis", "Dis");
            interfaceName = FixName(interfaceName);

            if (IsInterfaceRegistered(interfaceName))
            {
                var iFace = GetInterface(interfaceName);
                var sb = new StringBuilder();
                bool needsBaseClass = iFace.AdditionalInterfaces.Count > 0;

                if (!string.IsNullOrEmpty(iFace.CalaisType))
                {
                    sb.AppendFormat("  [CalaisType(\"{0}\")]", iFace.CalaisType);
                    sb.AppendLine();
                }
                if (!string.IsNullOrEmpty(iFace.CalaisDescription))
                {
                    sb.AppendFormat("  [CalaisTypeDescription(\"{0}\")]", iFace.CalaisDescription);
                    sb.AppendLine();
                }
                if (iFace.IsCustomType)
                {
                    sb.AppendFormat("  [CalaisType(\"http://calaisdotnet.codeplex.com/types/{0}\")]", iFace.Name.TrimStart('I'));
                    sb.AppendLine();
                }

                sb.AppendFormat("  public class {0}", iFace.Name.TrimStart('I'));

                sb.Append(" : ");

                if (needsBaseClass)
                {
                    sb.Append(customTypeBaseName + ", ");
                }

                sb.Append(iFace.Name);

                sb.AppendLine();
                sb.AppendLine("  {");

                foreach (var property in iFace.Properties)
                {
                    if (needsBaseClass)
                    {
                        sb.AppendFormat("\tpublic {0} {1}", property.Value, property.Key);
                        sb.AppendLine();
                        sb.AppendLine("\t{");
                        sb.AppendLine("\t\tget");
                        sb.AppendLine("\t\t{");
                        sb.AppendLine("\t\t\t return base." + property.Key + "; ");
                        sb.AppendLine("\t\t}");
                        sb.AppendLine();
                        sb.AppendLine("\t\tset");
                        sb.AppendLine("\t\t{");
                        sb.AppendLine("\t\t\t base." + property.Key + " = value; ");
                        sb.AppendLine("\t\t}");
                        sb.AppendLine("\t}");
                    }
                    else
                    {
                        sb.AppendFormat("\tpublic {0} {1}", property.Value, property.Key);
                        sb.AppendLine(" { get; set; }");
                    }
                }

                sb.AppendLine("  }");

                return sb.ToString();
            }

            return "Unknown interface [ " + interfaceName + " ]";

        }

        public string GenerateInterface(string interfaceName)
        {
            interfaceName = interfaceName.Replace("-dis", "Dis");

            if (IsInterfaceRegistered(interfaceName))
            {
                var iface = GetInterface(interfaceName);

                StringBuilder sb = new StringBuilder();

                if (!string.IsNullOrEmpty(iface.CalaisType))
                {
                    sb.AppendFormat("  [CalaisType(\"{0}\")]", iface.CalaisType);
                    sb.AppendLine();
                }
                if (!string.IsNullOrEmpty(iface.CalaisDescription))
                {
                    sb.AppendFormat("  [CalaisTypeDescription(\"{0}\")]", iface.CalaisDescription);
                    sb.AppendLine();
                }
                if (iface.IsCustomType)
                {
                    sb.AppendFormat("  [CalaisType(\"http://calaisdotnet.codeplex.com/types/{0}\")]", iface.Name);
                    sb.AppendLine();
                }

                sb.AppendFormat("  public interface {0}", iface.Name);

                sb.AppendLine();
                sb.AppendLine("  {");

                foreach (var property in iface.Properties)
                {
                    sb.AppendFormat("\t{0} {1}", property.Value, property.Key);
                    sb.AppendLine(" { get; set; }");
                }

                sb.AppendLine("  }");

                return sb.ToString();
            }

            return "Unknown interface [ " + interfaceName + " ]";
        }
    }

    /// <summary>
    /// Useful extension methods for creating contracts inside methods.
    /// </summary>
    public static class Predicates
    {
        internal static void Require<T>(this T obj, Func<T, bool> pred, Exception ex)
        {
            if (!pred(obj))
            {
                throw ex;
            }
        }

        internal static void Ensure<T>(this T obj, Func<T, bool> pred, Exception ex)
        {
            if (!pred(obj))
            {
                throw ex;
            }
        }
    }
}
